package main

import (
    "google.golang.org/grpc"
    "../pb"
    "sync"
    "time"
    "context"
    "io"
    "fmt"
    "../watcher"
)

type NodeInfo struct {
    commands map[string]string
    status   *pb.Status
}

type NodeWatcher struct {
    mu              sync.RWMutex
    addr            string
    conn            *grpc.ClientConn
    cli             pb.WatcherServiceClient
    lastUpdateTime  time.Time
    refreshInterval int
    nodeInfo        map[string]NodeInfo
    err             error
    token           *pb.Token
    closing         chan interface{}
    closed          chan interface{}
}

var CheckFormat = "  %12s  |  %12s  |  %12s    |  %12s"

func (nw *NodeWatcher) Status(procName string) (ret []string) {
    nw.mu.RLock()
    defer nw.mu.RUnlock()
    info, ok := nw.nodeInfo[procName]
    checkTime := time.Now().Sub(nw.lastUpdateTime).String() + " ago"
    if !ok {
        return []string{fmt.Sprintf("%s, proc name: %s ,not found", checkTime, procName)}
    }
    if info.status == nil {
        return []string{fmt.Sprintf("%s, proc name: %s ,status N/a", checkTime, procName)}
    }
    s := watcher.Formarted(info.status)
    return append([]string{fmt.Sprintf("%s, proc name: %s ,status:", checkTime, procName)}, s...)
}

func (nw *NodeWatcher) Check(nodeName string, less bool) (ret []string) {
    nw.mu.RLock()
    defer nw.mu.RUnlock()
    checkTime := time.Now().Sub(nw.lastUpdateTime).String() + " ago"
    line := fmt.Sprintf(CheckFormat, nodeName, "---", nw.err.Error(), checkTime) //node proc stat
    ret = append(ret, line)
    for procName, info := range nw.nodeInfo {
        stat := "N/a"
        count := int32(0)
        if info.status != nil {
            count = info.status.ProcCount
            stat = fmt.Sprintf("%s:(%d)", info.status.ProcType, info.status.ProcCount)
        }
        if less && count > 0 {
            continue
        }
        line := fmt.Sprintf(CheckFormat, nodeName, procName, stat, "---") //node proc stat
        ret = append(ret, line)
    }
    return
}

func (nw *NodeWatcher) reconnect() error {
    conn, err := grpc.Dial(nw.addr, grpc.WithInsecure())
    if err != nil {
        return err
    }
    nw.conn = conn
    nw.cli = pb.NewWatcherServiceClient(conn)
    return nil
}

func (nw *NodeWatcher) refresh() {
    nw.mu.Lock()
    defer nw.mu.Unlock()
    nw.lastUpdateTime = time.Now()
    nw.err = nil
    if nw.cli == nil {
        if err := nw.reconnect(); err != nil {
            nw.err = err
            return
        }
    }
    if len(nw.nodeInfo) == 0 {
        nw.nodeInfo = make(map[string]NodeInfo)
        resp, err := nw.cli.List(context.Background(), &pb.ListRequest{nw.token})
        if err != nil {
            nw.err = err
            return
        }
        for _, proc := range resp.ProcName {
            resp, err := nw.cli.Show(context.Background(), &pb.ShowRequest{nw.token, proc, ""})
            if err != nil {
                nw.err = err
                return
            }
            nw.nodeInfo[proc] = NodeInfo{
                commands: resp.Commands,
            }
        }
    }
    for proc, nd := range nw.nodeInfo {
        resp, err := nw.cli.Query(context.Background(), &pb.QueryWatcherRequest{nw.token, proc, true})
        if err != nil {
            nw.err = err
            return
        }
        nd.status = resp.Status
    }
}

func (nw *NodeWatcher) Commands(proc string) (ret []string) {
    nw.mu.Lock()
    defer nw.mu.Unlock()
    info, ok := nw.nodeInfo[proc]
    if !ok {
        return
    }
    for k, v := range info.commands {
        l := fmt.Sprintf("command name: %s  content: %s", k, v)
        ret = append(ret, l)
    }
    return
}

func (nw *NodeWatcher) Run(proc, command string, w io.Writer) (err error) {
    resp, err := nw.cli.Run(context.Background(),
        &pb.RunRequest{nw.token, proc, command})
    if err != nil {
        return err
    }
    for {
        response, err1 := resp.Recv()
        if err1 == io.EOF {
            if response.Finished {
                //
            } else {
                err = fmt.Errorf("broken connection")
            }
            break
        }
        if err1 != nil {
            err = err1
            break
        }
        for _, l := range response.Line {
            io.WriteString(w, l+"\n")
        }
    }
    return
}

func (nw *NodeWatcher) Stop() {
    close(nw.closing)
    <-nw.closed
}

func (nw *NodeWatcher) Watch() {
    tk := time.Tick(time.Duration(nw.refreshInterval) * time.Second)
    nw.refresh()
    for {
        select {
        case <-tk:
            nw.refresh()
        case <-nw.closing:
            nw.conn.Close()
            close(nw.closed)
            return
        }
    }
}
